﻿//MarketDataStorageHelper.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Algo.Storages
{
	using System;
	using System.Collections.Generic;

	using Ecng.Collections;
	using Ecng.Common;

	using StockSharp.BusinessEntities;

	static class MarketDataStorageHelper
	{
		public static decimal SerializeDecimal(this List<bool> bits, decimal price, decimal prevPrice)
		{
			var diff = price - prevPrice;

			if (price != prevPrice + diff)
				throw new ArgumentOutOfRangeException("price", "Разница между ценами {0} и {1} превысило размерность decimal.".Put(price, prevPrice));

			bits.Add(diff >= 0);

			if (diff < 0)
				diff = -diff;

			var decBits = decimal.GetBits(diff);

			bits.SerializeInt(decBits[0]);
			bits.SerializeInt(decBits[1]);
			bits.SerializeInt(decBits[2]);
			bits.SerializeInt((decBits[3] >> 16) & 0xff);

			return price;
		}

		public static decimal DeserializeDecimal(this BitArrayReader reader, decimal prevPrice)
		{
			var isPos = reader.Read();

			var diff = new decimal(new[] { reader.DeserializeInt(), reader.DeserializeInt(), reader.DeserializeInt(), reader.DeserializeInt() << 16 });

			if (!isPos)
				diff = -diff;

			return prevPrice + diff;
		}

		public static void SerializePrice<T>(this List<bool> bits, decimal price, MetaInfo<T> info, Security security)
			where T : MetaInfo<T>
		{
			bits.SerializePrice(price, info.LastPrice, info.MinStepSize, security);
			info.LastPrice = price;
		}

		public static void SerializePrice(this List<bool> bits, decimal price, decimal prevPrice, decimal minStepSize, Security security)
		{
			if ((price % minStepSize) != 0)
				throw new ArgumentException("Минимальный шаг цены {0} инструмента {1} не соответствует самой цене {2}.".Put(minStepSize, security, price), "minStepSize");

			var stepCount = (int)((price - prevPrice) / minStepSize);

			SerializeInt(bits, stepCount);
		}

		public static decimal DeserializePrice<T>(this BitArrayReader reader, MetaInfo<T> info)
			where T : MetaInfo<T>
		{
			return reader.DeserializePrice(info.FirstPrice, info.MinStepSize);
		}

		public static decimal DeserializePrice(this BitArrayReader reader, decimal prevPrice, decimal minStepSize)
		{
			return prevPrice + DeserializeInt(reader) * minStepSize;
		}

		public static void SerializePriceEx<T>(this List<bool> bits, decimal price, MetaInfo<T> info, Security security)
			where T : MetaInfo<T>
		{
			if (info.Version < MarketDataVersions.Version41)
			{
				bits.SerializePrice(price, info, security);
			}
			else
			{
				var isAligned = (price % info.MinStepSize) == 0;
				bits.Add(isAligned);

				if (isAligned)
				{
					if (info.FirstPrice == 0)
						info.FirstPrice = info.LastPrice = price;

					bits.SerializePrice(price, info, security);
				}
				else
				{
					if (info.FirstNonSystemPrice == 0)
						info.FirstNonSystemPrice = info.PrevNonSystemPrice = price;

					info.PrevNonSystemPrice = bits.SerializeDecimal(price, info.PrevNonSystemPrice);
				}
			}
		}

		public static decimal DeserializePriceEx<T>(this BitArrayReader reader, MetaInfo<T> info)
			where T : MetaInfo<T>
		{
			if (info.Version < MarketDataVersions.Version41)
			{
				return info.FirstPrice = reader.DeserializePrice(info);
			}
			else
			{
				if (reader.Read())
				{
					return info.FirstPrice = reader.DeserializePrice(info);
				}
				else
				{
					return info.FirstNonSystemPrice = reader.DeserializeDecimal(info.FirstNonSystemPrice);
				}
			}
		}

		public static long SerializeId(this List<bool> bits, long id, long prevId)
		{
			bits.SerializeLong(id - prevId);
			return id;
		}

		public static void AlignReader(this BitArrayReader reader)
		{
			if ((reader.Offset % 8) != 0)
			{
				var shift = ((reader.Offset / 8) * 8 + 8) - reader.Offset;
				reader.Offset += shift;
			}
		}

		public static void SerializeInt(this List<bool> bits, int value)
		{
			if (value == 0)
				bits.Add(false);
			else
			{
				bits.Add(true);

				if (value < 0)
				{
					value = -value;
					bits.Add(false);
				}
				else
					bits.Add(true);

				if (value == 1)
					bits.Add(false);
				else
				{
					bits.Add(true);
					if (value < 16)
					{
						bits.Add(false);
						bits.AddRange(value.ToBits(4));
					}
					else
					{
						bits.Add(true);
						if (value <= byte.MaxValue)
						{
							bits.Add(false);
							bits.AddRange(value.ToBits(8));
						}
						else
						{
							bits.Add(true);
							if (value <= ushort.MaxValue)
							{
								bits.Add(false);
								bits.AddRange(value.ToBits(16));
							}
							else
							{
								bits.Add(true);
								if (value <= 16777216) // 24 бита
								{
									bits.Add(false);
									bits.AddRange(value.ToBits(24));
								}
								else
								{
									bits.Add(true);
									bits.AddRange(value.ToBits(32));
								}
							}
						}
					}
				}
			}
		}

		public static int DeserializeInt(this BitArrayReader reader)
		{
			var optReader = reader as OptimizedBitArrayReader;
			return optReader != null ? optReader.ReadInt() : reader.DeserializeIntImpl();
		}

		private static int DeserializeIntImpl(this BitArrayReader reader)
		{
			var bits = (uint)reader.Lookahead();

			var value = 0;
			int seek;
			var read = 0;

			// смысл вынесения вызовов из if()-ов - заставить JIT заинлайнить .Seek()

			if ((bits & 1) == 0)
			{
				seek = 1;
			}
			else if ((bits & 4) == 0)
			{
				seek = 3;
				value = 1;
			}
			else if ((bits & 8) == 0)
			{
				seek = 4;
				read = 4;
			}
			else if ((bits & 16) == 0)
			{
				seek = 5;
				read = 8;
			}
			else if ((bits & 32) == 0)
			{
				seek = 6;
				read = 16;
			}
			else if ((bits & 64) == 0)
			{
				seek = 7;
				read = 24;
			}
			else
			{
				seek = 7;
				read = 32;
			}

			reader.Seek(seek);

			if (read > 0)
				value = reader.Read(read);

			bool positive = (bits & 2) != 0;

			return positive ? value : -value;

		}

		public static void SerializeLong(this List<bool> bits, long value)
		{
			if (value.Abs() > int.MaxValue)
			{
				bits.Add(true);
				bits.Add(value >= 0);
				bits.AddBits(value.Abs(), 63);
			}
			else
			{
				bits.Add(false);
				SerializeInt(bits, (int)value);
			}
		}

		public static long DeserializeLong(this BitArrayReader reader)
		{
			var optReader = reader as OptimizedBitArrayReader;
			return optReader != null ? optReader.ReadLong() : reader.DeserializeLongImpl();
		}

		private static long DeserializeLongImpl(this BitArrayReader reader)
		{
			if (reader.Read())
			{
				var isPositive = reader.Read();

				var value = reader.ReadLong(63);

				if (!isPositive)
					value = -value;

				return value;
			}
			else
			{
				return DeserializeInt(reader);
			}
		}

		public static DateTime Truncate(this DateTime time)
		{
			return time.Truncate(TimeSpan.TicksPerMillisecond);
		}

		public static DateTime SerializeTime(this List<bool> bits, DateTime time, DateTime prevTime, string name)
		{
			if (bits == null)
				throw new ArgumentNullException("bits");

			time = time.Truncate();

			var timeDiff = time - prevTime;

			if (timeDiff < TimeSpan.Zero)
				throw new ArgumentException("Попытка записать неупорядоченные {0}. Последнее записанное время равно {1:yyyy/MM/dd HH:mm:ss.fff}, новое {2:yyyy/MM/dd HH:mm:ss.fff}.".Put(name, prevTime, time), "time");
			
			if (timeDiff >= TimeSpan.FromMinutes(1))
			{
				bits.Add(true);

				timeDiff = time.TimeOfDay;

				bits.AddBits(timeDiff.Hours, 5);
				bits.AddBits(timeDiff.Minutes, 6);
				bits.AddBits(timeDiff.Seconds, 6);
			}
			else
			{
				bits.Add(false);

				SerializeInt(bits, timeDiff.Seconds);
			}

			SerializeInt(bits, timeDiff.Milliseconds);

			return time;
		}

		public static DateTime DeserializeTime(this BitArrayReader reader, DateTime date, DateTime prevTime)
		{
			var optReader = reader as OptimizedBitArrayReader;
			return optReader != null
			       	? optReader.ReadTime(date, prevTime)
			       	: DeserializeTimeImpl(reader, date, prevTime);
		}

		private static DateTime DeserializeTimeImpl(this BitArrayReader reader, DateTime date, DateTime prevTime)
		{
			//if (reader == null)
			//	throw new ArgumentNullException("reader");

			if (reader.Read())
			{
				prevTime = date + new TimeSpan(reader.Read(5), reader.Read(6), reader.Read(6));
			}
			else
			{
				prevTime += TimeSpan.FromSeconds(DeserializeInt(reader));
			}

			prevTime += TimeSpan.FromMilliseconds(DeserializeInt(reader));

			return prevTime;
		}

		public static void SerializeVolume(this List<bool> bits, decimal volume)
		{
			var intVolume = volume.Truncate();

			if (intVolume == volume) // объем целочисленный
			{
				bits.Add(true);
				SerializeLong(bits, (long)intVolume);
			}
			else
			{
				bits.Add(false);
				throw new NotImplementedException("Сохранение дробного объема {0} не реализовано.".Put(volume));
			}
		}

		public static decimal DeserializeVolume(this BitArrayReader reader)
		{
			if (reader.Read())
				return DeserializeLong(reader);
			else
				throw new NotImplementedException("Загрузка дробного объема не реализована.");
		}

		public static long InlinedDeserializeVolume(this BitArrayReader reader)
		{
			var bits = (uint)reader.Lookahead();

			if ((bits & 1) == 0)
				throw new NotImplementedException("Загрузка дробного объема не реализована.");

			if ((bits & 2) != 0)
			{
				var isPositive = (bits & 4) != 0;

				reader.Seek(3);
				var value = reader.ReadLong(63);

				if (!isPositive)
					value = -value;

				return value;
			}
			else
			{
				int value = 0;
				int seek;
				int read;

				if ((bits & 4) == 0)
				{
					seek = 3;
					read = 0;
					// value=0
				}
				else if ((bits & 16) == 0)
				{
					seek = 5;
					read = 0;
					value = 1;
				}
				else if ((bits & 32) == 0)
				{
					seek = 6;
					read = 4;
				}
				else if ((bits & 64) == 0)
				{
					seek = 7;
					read = 8;
				}
				else if ((bits & 128) == 0)
				{
					seek = 8;
					read = 16;
				}
				else if ((bits & 256) == 0)
				{
					seek = 9;
					read = 24;
				}
				else
				{
					seek = 9;
					read = 32;
				}

				reader.Seek(seek);

				if (read > 0)
					value = reader.Read(read);

				bool positive = (bits & 8) != 0;
				return positive ? value : -value;
			}
		}

		public static void SerializeTradeDirection(this List<bool> bits, OrderDirections? direction)
		{
			if (direction == null)
				bits.Add(false);
			else
			{
				bits.Add(true);
				bits.Add(direction == OrderDirections.Buy);
			}
		}

		public static OrderDirections? DeserializeTradeDirection(this BitArrayReader reader)
		{
			return reader.Read() ? (reader.Read() ? OrderDirections.Buy : OrderDirections.Sell) : (OrderDirections?)null;
		}
	}

	class OptimizedBitArrayReader : BitArrayReader
	{
		public OptimizedBitArrayReader(byte[] data)
			: base(data)
		{
		}

		public int ReadInt()
		{
			var offset = DataOffset;
			var bits = Data[offset];
			var bitOffset = BitOffset;

			bits >>= bitOffset;

			if (bitOffset > 0)
				bits |= Data[offset + 1] << (64 - bitOffset); // честные 64 бита в битс

			var value = 0;

			int seek;

			if ((bits & 1) == 0)
			{
				seek = 1;
			}
			else if ((bits & 4) == 0)
			{
				seek = 3;
				value = 1;
			}
			else if ((bits & 8) == 0)
			{
				seek = 4 + 4;
				value = ((int)(uint.MaxValue >> (32 - 4))) & (int)(bits >> 4);
			}
			else if ((bits & 16) == 0)
			{
				seek = 5 + 8;
				value = ((int)(uint.MaxValue >> (32 - 8))) & (int)(bits >> 5);
			}
			else if ((bits & 32) == 0)
			{
				seek = 6 + 16;
				value = ((int)(uint.MaxValue >> (32 - 16))) & (int)(bits >> 6);
			}
			else if ((bits & 64) == 0)
			{
				seek = 7 + 24;
				value = ((int)(uint.MaxValue >> (32 - 24))) & (int)(bits >> 7);
			}
			else
			{
				seek = 7 + 32;
				value = (int)(bits >> 7);
			}

			value = (bits & 2) != 0 ? value : -value;

			bitOffset += seek;
			DataOffset = offset + (bitOffset >> 6);
			BitOffset = bitOffset & 63;

			return value;
		}

		public long ReadLong()
		{
			var offset = DataOffset;
			var bitOffset = BitOffset;
			var bits = Data[offset] >> bitOffset;

			if (bitOffset > 0)
				bits |= Data[offset + 1] << (64 - bitOffset);

			long value;
			if ((bits & 1) != 0)
			{
				var isPositive = (bits & 2) != 0;

				bitOffset += 2;

				offset += bitOffset >> 6;
				bitOffset &= 63;
				bits = Data[offset] >> bitOffset;

				if (bitOffset > 0)
					bits |= Data[offset + 1] << (64 - bitOffset);

				bitOffset += 63;

				value = (long)(bits & (ulong.MaxValue >> (64 - 63)));

				if (!isPositive)
					value = -value;

			}
			else
			{
				bitOffset += 1;
				bits >>= 1;

				int seek;

				value = 0;

				if ((bits & 1) == 0)
				{
					seek = 1;
				}
				else if ((bits & 4) == 0)
				{
					seek = 3;
					value = 1;
				}
				else if ((bits & 8) == 0)
				{
					seek = 4 + 4;
					value = ((int)(uint.MaxValue >> (32 - 4))) & (int)(bits >> 4);
				}
				else if ((bits & 16) == 0)
				{
					seek = 5 + 8;
					value = ((int)(uint.MaxValue >> (32 - 8))) & (int)(bits >> 5);
				}
				else if ((bits & 32) == 0)
				{
					seek = 6 + 16;
					value = ((int)(uint.MaxValue >> (32 - 16))) & (int)(bits >> 6);
				}
				else if ((bits & 64) == 0)
				{
					seek = 7 + 24;
					value = ((int)(uint.MaxValue >> (32 - 24))) & (int)(bits >> 7);
				}
				else
				{
					seek = 7 + 32;
					value = (int)(bits >> 7);
				}

				value = (bits & 2) != 0 ? value : -value;
				bitOffset += seek;
			}

			offset += bitOffset >> 6;
			bitOffset &= 63;
			DataOffset = offset;
			BitOffset = bitOffset;

			return value;
		}

		public void ReadPriceVolume(out int price, out long volume)
		{
			var offset = DataOffset;
			var bits = Data[offset];
			var bitOffset = BitOffset;

			bits >>= bitOffset;

			if (bitOffset > 0)
				bits |= Data[offset + 1] << (64 - bitOffset); // честные 64 бита в битс

			price = 0;

			int seek;

			if ((bits & 1) == 0)
			{
				seek = 1;
			}
			else if ((bits & 4) == 0)
			{
				seek = 3;
				price = 1;
			}
			else if ((bits & 8) == 0)
			{
				seek = 4 + 4;
				price = ((int)(uint.MaxValue >> (32 - 4))) & (int)(bits >> 4);
			}
			else if ((bits & 16) == 0)
			{
				seek = 5 + 8;
				price = ((int)(uint.MaxValue >> (32 - 8))) & (int)(bits >> 5);
			}
			else if ((bits & 32) == 0)
			{
				seek = 6 + 16;
				price = ((int)(uint.MaxValue >> (32 - 16))) & (int)(bits >> 6);
			}
			else if ((bits & 64) == 0)
			{
				seek = 7 + 24;
				price = ((int)(uint.MaxValue >> (32 - 24))) & (int)(bits >> 7);
			}
			else
			{
				seek = 7 + 32;
				price = (int)(bits >> 7);
			}

			price = (bits & 2) != 0 ? price : -price;

			bitOffset += seek;
			offset += bitOffset >> 6;

			bitOffset &= 63;
			bits = Data[offset] >> bitOffset;
			if (bitOffset > 0)
				bits |= Data[offset + 1] << (64 - bitOffset);

			if ((bits & 1) == 0)
				throw new NotImplementedException("Загрузка дробного объема не реализована.");

			if ((bits & 2) != 0)
			{
				var isPositive = (bits & 4) != 0;

				bitOffset += 3;

				offset += bitOffset >> 6;
				bitOffset &= 63;
				bits = Data[offset] >> bitOffset;

				if (bitOffset > 0)
					bits |= Data[offset + 1] << (64 - bitOffset);

				bitOffset += 63;

				volume = (long)(bits & (ulong.MaxValue >> (64 - 63)));

				if (!isPositive)
					volume = -volume;
			}
			else
			{
				bitOffset += 2;
				bits >>= 2;
				volume = 0;

				if ((bits & 1) == 0)
				{
					seek = 1;
				}
				else if ((bits & 4) == 0)
				{
					seek = 3;
					volume = 1;
				}
				else if ((bits & 8) == 0)
				{
					seek = 4 + 4;
					volume = ((int)(uint.MaxValue >> (32 - 4))) & (int)(bits >> 4);
				}
				else if ((bits & 16) == 0)
				{
					seek = 5 + 8;
					volume = ((int)(uint.MaxValue >> (32 - 8))) & (int)(bits >> 5);
				}
				else if ((bits & 32) == 0)
				{
					seek = 6 + 16;
					volume = ((int)(uint.MaxValue >> (32 - 16))) & (int)(bits >> 6);
				}
				else if ((bits & 64) == 0)
				{
					seek = 7 + 24;
					volume = ((int)(uint.MaxValue >> (32 - 24))) & (int)(bits >> 7);
				}
				else
				{
					seek = 7 + 32;
					volume = (int)(bits >> 7);
				}

				volume = (bits & 2) != 0 ? volume : -volume;
				bitOffset += seek;
			}

			offset += bitOffset >> 6;
			bitOffset &= 63;
			DataOffset = offset;
			BitOffset = bitOffset;
		}

		public DateTime ReadTime(DateTime date, DateTime prevTime)
		{
			var offset = DataOffset;
			var bits = Data[offset];
			var bitOffset = BitOffset;

			bits >>= bitOffset;

			if (bitOffset > 0)
				bits |= Data[offset + 1] << (64 - bitOffset); // честные 64 бита в битс

			int seek, value;

			long addTicks = 0;

			if ((bits & 1) != 0)
			{
				var h = (int)((bits >> 1) & (ulong.MaxValue >> 64 - 5));
				var m = (int)((bits >> 6) & (ulong.MaxValue >> 64 - 6));
				var s = (int)((bits >> 12) & (ulong.MaxValue >> 64 - 6));

				bitOffset += 1 + 5 + 6 + 6;
				prevTime = date + new TimeSpan(h, m, s);
			}
			else
			{
				bits >>= 1;
				bitOffset += 1;

				value = 0;
				if ((bits & 1) == 0)
				{
					seek = 1;
				}
				else if ((bits & 4) == 0)
				{
					seek = 3;
					value = 1;
				}
				else if ((bits & 8) == 0)
				{
					seek = 4 + 4;
					value = ((int)(uint.MaxValue >> (32 - 4))) & (int)(bits >> 4);
				}
				else if ((bits & 16) == 0)
				{
					seek = 5 + 8;
					value = ((int)(uint.MaxValue >> (32 - 8))) & (int)(bits >> 5);
				}
				else if ((bits & 32) == 0)
				{
					seek = 6 + 16;
					value = ((int)(uint.MaxValue >> (32 - 16))) & (int)(bits >> 6);
				}
				else if ((bits & 64) == 0)
				{
					seek = 7 + 24;
					value = ((int)(uint.MaxValue >> (32 - 24))) & (int)(bits >> 7);
				}
				else
				{
					seek = 7 + 32;
					value = (int)(bits >> 7);
				}
				value = (bits & 2) != 0 ? value : -value;
				bitOffset += seek;

				//prevTime = prevTime.AddSeconds(value);
				addTicks = value * TimeSpan.TicksPerSecond;
			}

			offset += bitOffset >> 6;

			bitOffset &= 63;

			bits = Data[offset] >> bitOffset;

			if (bitOffset > 0)
				bits |= Data[offset + 1] << (64 - bitOffset);

			value = 0;
			if ((bits & 1) == 0)
			{
				seek = 1;
			}
			else if ((bits & 4) == 0)
			{
				seek = 3;
				value = 1;
			}
			else if ((bits & 8) == 0)
			{
				seek = 4 + 4;
				value = ((int)(uint.MaxValue >> (32 - 4))) & (int)(bits >> 4);
			}
			else if ((bits & 16) == 0)
			{
				seek = 5 + 8;
				value = ((int)(uint.MaxValue >> (32 - 8))) & (int)(bits >> 5);
			}
			else if ((bits & 32) == 0)
			{
				seek = 6 + 16;
				value = ((int)(uint.MaxValue >> (32 - 16))) & (int)(bits >> 6);
			}
			else if ((bits & 64) == 0)
			{
				seek = 7 + 24;
				value = ((int)(uint.MaxValue >> (32 - 24))) & (int)(bits >> 7);
			}
			else
			{
				seek = 7 + 32;
				value = (int)(bits >> 7);
			}

			value = (bits & 2) != 0 ? value : -value;
			bitOffset += seek;

			addTicks += value * TimeSpan.TicksPerMillisecond;
			//prevTime = prevTime.AddMilliseconds(value);
			prevTime = prevTime.AddTicks(addTicks);

			offset += bitOffset >> 6;
			bitOffset &= 63;
			DataOffset = offset;
			BitOffset = bitOffset;

			return prevTime;
		}
	}
}